/*
 * Copyright 2014 Jens K., Jalbum AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.package net.jalbum.jprint;
 */
package net.jalbum.areatagging;

import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMeta;
import com.adobe.xmp.options.PropertyOptions;
import java.awt.BasicStroke;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import static net.jalbum.areatagging.AreaTagHandler.MS_PHOTO_NS;
import static net.jalbum.areatagging.AreaTagHandler.MWG_RS_NS;

/**
 * An instance of a single area tag rectangle (x + y + width w + height h).
 */
public class AreaTagRectangle extends AreaTag {

    private float w, h;

    public AreaTagRectangle(String name, String description, String type, float x, float y, float w, float h) {
        setName(name);
        setDescription(description);
        setType(type);
        setX(x);
        setY(y);
        this.w = w;
        this.h = h;
    }

    public float getW() {
        return w;
    }

    public float getH() {
        return h;
    }

    public void setW(float w) {
        this.w = w;
    }

    public void setH(float h) {
        this.h = h;
    }

    public void setxWeight(float xWeight) {
        float min = 0.0f + (w / 2.0f);
        float max = 1.0f - (w / 2.0f);
        setX(capValue(xWeight, min, max));
    }

    public void setyWeight(float yWeight) {
        float min = 0.0f + (h / 2.0f);
        float max = 1.0f - (h / 2.0f);
        setY(capValue(yWeight, min, max));
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(super.toString()).append("\n");
        buf.append("\tw-position of tag: \t ").append(w).append("\n");
        buf.append("\th-position of tag: \t ").append(h).append("\n");

        return buf.toString();
    }

    @Override
    public void drawInactive(Graphics2D big, Dimension sDim) {
        // create tag Rectangle to draw
        Rectangle inactiveRect = new Rectangle();
        inactiveRect.x = Math.round((sDim.width * getX()) - (sDim.width * w) / 2.0f);
        inactiveRect.y = Math.round((sDim.height * getY()) - (sDim.height * h) / 2.0f);
        inactiveRect.width = Math.round(sDim.width * w);
        inactiveRect.height = Math.round(sDim.height * h);

        // draw border
        big.setColor(getDimmedG());
        big.setStroke(new BasicStroke(getBorderInactive()));
        big.drawRoundRect(inactiveRect.x - getBorderInactive(), inactiveRect.y - getBorderInactive(),
                inactiveRect.width + getBorderInactive() * 2, inactiveRect.height + getBorderInactive() * 2,
                getBorderInactive(), getBorderInactive());

        // draw border shadow
        big.setColor(getDimmedBsoft());
        big.setStroke(new BasicStroke(getShadow()));
        big.drawRoundRect(inactiveRect.x - getBorderInactive() + getShadow(),
                inactiveRect.y - getBorderInactive() + getShadow(),
                inactiveRect.width + getBorderInactive() * 2 - getShadow() * 2,
                inactiveRect.height + getBorderInactive() * 2 - getShadow() * 2,
                getBorderInactive(), getBorderInactive());

        if (getName() != null && !getName().isEmpty()) {
            // prepare font
            big.setFont(getFont());
            FontMetrics fm = big.getFontMetrics();

            // create name Rectangle to draw
            Rectangle nameBounds = (fm.getStringBounds(getName(), big)).getBounds();
            nameBounds.x = inactiveRect.x + (inactiveRect.width / 2) - (nameBounds.width / 2);
            nameBounds.y = inactiveRect.y + inactiveRect.height + (3 * getBorderActive());

            // change x and y depending on the position of the tag (should not be outside image)
            if (nameBounds.y > sDim.height) {
                nameBounds.y = inactiveRect.y - (2 * getBorderActive());
            }
            if (nameBounds.x < 0) {
                nameBounds.x = 0;
            }
            if (nameBounds.x + nameBounds.width > sDim.width) {
                nameBounds.x = sDim.width - nameBounds.width;
            }

            // draw outer border
            big.setColor(getDimmedBsoft());
            big.fillRoundRect(nameBounds.x - getSpace(), nameBounds.y - nameBounds.height,
                    nameBounds.width + getSpace() * 2, nameBounds.height + getSpace() * 2,
                    getSpace() * 2, getSpace() * 2);

            // draw name
            big.setColor(getDimmedG());
            big.drawString(getName(), nameBounds.x, nameBounds.y);
        }
    }

    @Override
    public void drawActive(Graphics2D big, Dimension sDim, String name) {
        // create tag Rectangle to draw
        Rectangle activeRect = new Rectangle();
        activeRect.x = Math.round((sDim.width * getX()) - (sDim.width * w) / 2.0f);
        activeRect.y = Math.round((sDim.height * getY()) - (sDim.height * h) / 2.0f);
        activeRect.width = Math.round(sDim.width * w);
        activeRect.height = Math.round(sDim.height * h);

        // handle cross sizing
        activeRect.width = (int) Math.sqrt(activeRect.width * activeRect.width);
        activeRect.height = (int) Math.sqrt(activeRect.height * activeRect.height);
        if (activeRect.width < 0) {
            activeRect.x = activeRect.x - activeRect.width;
        }
        if (activeRect.height < 0) {
            activeRect.y = activeRect.y - activeRect.height;
        }

        // draw border
        big.setColor(getDimmedW());
        big.setStroke(new BasicStroke(getBorderActive()));
        big.drawRoundRect(activeRect.x - getBorderActive() / 2,
                activeRect.y - getBorderActive() / 2,
                activeRect.width + getBorderActive(),
                activeRect.height + getBorderActive(),
                getBorderActive(), getBorderActive());

        // draw border shadow
        big.setColor(getDimmedG());
        big.setStroke(new BasicStroke(getShadow()));
        big.drawRoundRect(activeRect.x, activeRect.y, activeRect.width, activeRect.height,
                getBorderActive() / 2, getBorderActive() / 2);

        // draw black size modification ovals
        big.setColor(getDimmedBsoft());
        big.fillOval(activeRect.x - getBorderActive() / 2, activeRect.y - getBorderActive() / 2,
                getBorderActive() + getBorderActive() / 4, getBorderActive() + getBorderActive() / 4);
        big.fillOval(activeRect.x + activeRect.width - getBorderActive() / 2, activeRect.y - getBorderActive() / 2,
                getBorderActive() + getBorderActive() / 4, getBorderActive() + getBorderActive() / 4);
        big.fillOval(activeRect.x - getBorderActive() / 2, activeRect.y + activeRect.height - getBorderActive() / 2,
                getBorderActive() + getBorderActive() / 4, getBorderActive() + getBorderActive() / 4);
        big.fillOval(activeRect.x + activeRect.width - getBorderActive() / 2, activeRect.y + activeRect.height - getBorderActive() / 2,
                getBorderActive() + getBorderActive() / 4, getBorderActive() + getBorderActive() / 4);

        if (name != null && !name.isEmpty()) {
            // prepare font
            big.setFont(getFont());
            FontMetrics fm = big.getFontMetrics();

            // create name Rectangle to draw
            Rectangle nameBounds = (fm.getStringBounds(name, big)).getBounds();
            nameBounds.x = activeRect.x + (activeRect.width / 2) - (nameBounds.width / 2);
            nameBounds.y = activeRect.y + activeRect.height + (3 * getBorderActive());

            // change x and y depending on the position of the tag (should not be outside image)
            if (nameBounds.y > sDim.height) {
                nameBounds.y = activeRect.y - (2 * getBorderActive());
            }
            if (nameBounds.x < 0) {
                nameBounds.x = 0;
            }
            if (nameBounds.x + nameBounds.width > sDim.width) {
                nameBounds.x = sDim.width - nameBounds.width;
            }

            // draw outer border
            big.setColor(getDimmedW());
            big.fillRoundRect(nameBounds.x - getSpace(), nameBounds.y - nameBounds.height,
                    nameBounds.width + getSpace() * 2, nameBounds.height + getSpace() * 2, getSpace() * 2, getSpace() * 2);

            // draw name
            big.setColor(getDimmedBhard());
            big.drawString(name, nameBounds.x, nameBounds.y);
        }
    }

    @Override
    int resizeOrMove(MouseState mouseLoc, Dimension sDim, Point p) {
        float areaX, areaY, areaW, areaH, newX, newY, newW, newH;
        int cursor = 0;

        // calculate actual tag position and dimension in absolute values
        areaX = getX() * sDim.width;
        areaY = getY() * sDim.height;
        areaW = w * sDim.width;
        areaH = h * sDim.height;

        switch (mouseLoc) {
            case NW:
                // move origin to lower right corner
                newX = areaX + areaW / 2;
                newY = areaY + areaH / 2;

                // calculate area width and height
                newW = areaW - p.x;
                newH = areaH - p.y;

                // calculate area x and y position
                newX = newX - newW / 2;
                newY = newY - newH / 2;

                // convert coordinates if negative width and/or height
                if ((newW < 0) && (newH < 0)) {
                    newW = newW * (-1);
                    newH = newH * (-1);
                    cursor = Cursor.SE_RESIZE_CURSOR;
                } else if (newW < 0) {
                    newW = newW * (-1);
                    cursor = Cursor.NE_RESIZE_CURSOR;
                } else if (newH < 0) {
                    newH = newH * (-1);
                    cursor = Cursor.SW_RESIZE_CURSOR;
                } else {
                    cursor = Cursor.NW_RESIZE_CURSOR;
                }

                // stop at image borders
                if ((newX - newW / 2 > 0) && (newX + newW / 2 < sDim.width)) {
                    w = 1.0f / sDim.width * newW;
                    setxWeight(1.0f / sDim.width * newX);
                }
                if ((newY - newH / 2 > 0) && (newY + newH / 2 < sDim.height)) {
                    h = 1.0f / sDim.height * newH;
                    setyWeight(1.0f / sDim.height * newY);
                }
                break;

            case NE:
                // move origin to lower left corner
                newX = areaX - areaW / 2;
                newY = areaY + areaH / 2;

                // calculate area width and height
                newW = areaW + p.x;
                newH = areaH - p.y;

                // calculate area x and y position
                newX = newX + newW / 2;
                newY = newY - newH / 2;

                // convert coordinates if negative width and/or height
                if ((newW < 0) && (newH < 0)) {
                    newW = newW * (-1);
                    newH = newH * (-1);
                    cursor = Cursor.SW_RESIZE_CURSOR;
                } else if (newW < 0) {
                    newW = newW * (-1);
                    cursor = Cursor.NW_RESIZE_CURSOR;
                } else if (newH < 0) {
                    newH = newH * (-1);
                    cursor = Cursor.SE_RESIZE_CURSOR;
                } else {
                    cursor = Cursor.NE_RESIZE_CURSOR;
                }

                // stop at image borders
                if ((newX - newW / 2 > 0) && (newX + newW / 2 < sDim.width)) {
                    w = 1.0f / sDim.width * newW;
                    setxWeight(1.0f / sDim.width * newX);
                }
                if ((newY - newH / 2 > 0) && (newY + newH / 2 < sDim.height)) {
                    h = 1.0f / sDim.height * newH;
                    setyWeight(1.0f / sDim.height * newY);
                }
                break;

            case SW:
                // move origin to upper right corner
                newX = areaX + areaW / 2;
                newY = areaY - areaH / 2;

                // calculate area width and height
                newW = areaW - p.x;
                newH = areaH + p.y;

                // calculate area x and y position
                newX = newX - newW / 2;
                newY = newY + newH / 2;

                // convert coordinates if negative width and/or height
                if ((newW < 0) && (newH < 0)) {
                    newW = newW * (-1);
                    newH = newH * (-1);
                    cursor = Cursor.NE_RESIZE_CURSOR;
                } else if (newW < 0) {
                    newW = newW * (-1);
                    cursor = Cursor.SE_RESIZE_CURSOR;
                } else if (newH < 0) {
                    newH = newH * (-1);
                    cursor = Cursor.NW_RESIZE_CURSOR;
                } else {
                    cursor = Cursor.SW_RESIZE_CURSOR;
                }

                // stop at image borders
                if ((newX - newW / 2 > 0) && (newX + newW / 2 < sDim.width)) {
                    w = 1.0f / sDim.width * newW;
                    setxWeight(1.0f / sDim.width * newX);
                }
                if ((newY - newH / 2 > 0) && (newY + newH / 2 < sDim.height)) {
                    h = 1.0f / sDim.height * newH;
                    setyWeight(1.0f / sDim.height * newY);
                }
                break;

            case SE:
                // move origin to upper left corner
                newX = areaX - areaW / 2;
                newY = areaY - areaH / 2;

                // calculate area width and height
                newW = areaW + p.x;
                newH = areaH + p.y;

                // calculate area x and y position
                newX = newX + newW / 2;
                newY = newY + newH / 2;

                // convert coordinates if negative width and/or height
                if ((newW < 0) && (newH < 0)) {
                    newW = newW * (-1);
                    newH = newH * (-1);
                    cursor = Cursor.NW_RESIZE_CURSOR;
                } else if (newW < 0) {
                    newW = newW * (-1);
                    cursor = Cursor.SW_RESIZE_CURSOR;
                } else if (newH < 0) {
                    newH = newH * (-1);
                    cursor = Cursor.NE_RESIZE_CURSOR;
                } else {
                    cursor = Cursor.SE_RESIZE_CURSOR;
                }

                // stop at image borders
                if ((newX - newW / 2 > 0) && (newX + newW / 2 < sDim.width)) {
                    w = 1.0f / sDim.width * newW;
                    setxWeight(1.0f / sDim.width * newX);
                }
                if ((newY - newH / 2 > 0) && (newY + newH / 2 < sDim.height)) {
                    h = 1.0f / sDim.height * newH;
                    setyWeight(1.0f / sDim.height * newY);
                }
                break;

            case OVER_ACTIVE:
                // move
                areaX = (sDim.width * getX()) + p.x;
                areaY = (sDim.height * getY()) + p.y;
                setxWeight(1.0f / sDim.width * areaX);
                setyWeight(1.0f / sDim.height * areaY);
                cursor = Cursor.HAND_CURSOR;
                break;
        }

        // signalize curser has to change
        return cursor;
    }

    @Override
    MouseState updateMouseState(Graphics2D g, Dimension sDim, Point p) {
        Rectangle activeRect = new Rectangle();
        activeRect.x = (int) ((sDim.width * getX()) - (sDim.width * getW()) / 2.0f);
        activeRect.y = (int) ((sDim.height * getY()) - (sDim.height * getH()) / 2.0f);
        activeRect.width = (int) (sDim.width * w);
        activeRect.height = (int) (sDim.height * h);

        int xStart = activeRect.x;
        int yStart = activeRect.y;
        int xEnd = activeRect.x + activeRect.width;
        int yEnd = activeRect.y + activeRect.height;

        if ((p.x > xStart - getBorderActive() / 2) && (p.x < xStart + getBorderActive() / 2)
                && (p.y > yStart - getBorderActive() / 2) && (p.y < yStart + getBorderActive() / 2)) {
            // mouse at north west corner
            return MouseState.NW;
        } else if ((p.x > xEnd - getBorderActive() / 2) && (p.x < xEnd + getBorderActive())
                && (p.y > yStart - getBorderActive()) && (p.y < yStart + getBorderActive() / 2)) {
            // mouse at north east corner
            return MouseState.NE;
        } else if ((p.x > xStart - getBorderActive() / 2) && (p.x < xStart + getBorderActive() / 2)
                && (p.y > yEnd - getBorderActive() / 2) && (p.y < yEnd + getBorderActive() / 2)) {
            // mouse at south west corner
            return MouseState.SW;
        } else if ((p.x > xEnd - getBorderActive() / 2) && (p.x < xEnd + getBorderActive() / 2)
                && (p.y > yEnd - getBorderActive() / 2) && (p.y < yEnd + getBorderActive() / 2)) {
            // mouse at south east corner
            return MouseState.SE;
        } else if (((p.x > xStart) && (p.x < xEnd) && (p.y > yStart) && (p.y < yEnd)) || isPointInsideName(g, sDim, p)) {
            // mouse inside picture
            return MouseState.OVER_ACTIVE;
        } else {
            // mouse outside picture
            return MouseState.OUTSIDE;
        }
    }

    @Override
    boolean isPointInsideTag(Graphics2D g, Dimension dim, Point p) {
        // create tag area
        Rectangle tagArea = new Rectangle();
        tagArea.x = (int) ((dim.width * getX()) - (dim.width * w) / 2.0f);
        tagArea.y = (int) ((dim.height * getY()) - (dim.height * h) / 2.0f);
        tagArea.width = (int) (dim.width * w);
        tagArea.height = (int) (dim.height * h);

        // deal with extremely small tags (enlarge the area to select them)
        int enlargerSize = 20;

        if (tagArea.width < enlargerSize) {
            int xEnlarger = enlargerSize - tagArea.width;
            tagArea.width = tagArea.width + xEnlarger;
            tagArea.x = tagArea.x - xEnlarger / 2;
        }

        if (tagArea.height < enlargerSize) {
            int yEnlarger = enlargerSize - tagArea.height;
            tagArea.height = tagArea.height + yEnlarger;
            tagArea.y = tagArea.y - yEnlarger / 2;
        }

        return tagArea.contains(p);
    }

    @Override
    boolean isPointInsideName(Graphics2D g, Dimension dim, Point p) {
        if (getName() != null && !getName().isEmpty()) {
            Rectangle tagArea = new Rectangle();
            tagArea.x = (int) ((dim.width * getX()) - (dim.width * w) / 2.0f);
            tagArea.y = (int) ((dim.height * getY()) - (dim.height * h) / 2.0f);
            tagArea.width = (int) (dim.width * w);
            tagArea.height = (int) (dim.height * h);

            // prepare font
            g.setFont(getFont());
            FontMetrics fm = g.getFontMetrics();

            // check if name was clicked
            Rectangle nameBounds = (fm.getStringBounds(getName(), g)).getBounds();
            nameBounds.x = tagArea.x + (tagArea.width / 2) - (nameBounds.width / 2);
            nameBounds.y = tagArea.y + tagArea.height + (3 * getBorderActive());

            // change x and y depending on the position of the tag (can not be outside image)
            if (nameBounds.y > dim.height) {
                nameBounds.y = tagArea.y - (2 * getBorderActive());
            }
            if (nameBounds.x < 0) {
                nameBounds.x = 0;
            }
            if (nameBounds.x + nameBounds.width > dim.width) {
                nameBounds.x = dim.width - nameBounds.width;
            }

            // create name area
            Rectangle nameArea = new Rectangle(nameBounds.x - getSpace(), nameBounds.y - nameBounds.height,
                    nameBounds.width + getSpace() * 2, nameBounds.height + getSpace() * 2);

            return nameArea.contains(p);
        }
        return false;
    }

    @Override
    void storeMWGAreaTags(XMPMeta meta, int item) throws XMPException {
        // convert and format tag information
        DecimalFormat f = new DecimalFormat("#0.000000", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
        double xF = (double) getX();
        xF = Double.parseDouble(f.format(xF));
        double yF = (double) getY();
        yF = Double.parseDouble(f.format(yF));
        double wF = (double) w;
        wF = Double.parseDouble(f.format(wF));
        double hF = (double) h;
        hF = Double.parseDouble(f.format(hF));

        String name = getName();
        String description = getDescription();
        String type = getType();

        // create array node
        meta.appendArrayItem(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList", new PropertyOptions().setArray(true), "", new PropertyOptions().setStruct(true));

        // store area information
        meta.setProperty(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + item + "]/mwg-rs:Area/stArea:unit", getUnit());
        meta.setPropertyDouble(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + item + "]/mwg-rs:Area/stArea:x", xF);
        meta.setPropertyDouble(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + item + "]/mwg-rs:Area/stArea:y", yF);
        meta.setPropertyDouble(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + item + "]/mwg-rs:Area/stArea:w", wF);
        meta.setPropertyDouble(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + item + "]/mwg-rs:Area/stArea:h", hF);

        // store additional information (if there are some)
        if ((name != null && !name.isEmpty())) {
            meta.setProperty(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + item + "]/mwg-rs:Name", name);
        }
        if ((description != null && !description.isEmpty())) {
            meta.setProperty(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + item + "]/mwg-rs:Description", description);
        }
        if ((type != null)) {
            meta.setProperty(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + item + "]/mwg-rs:Type", type);
        }

        item++;
    }

    @Override
    void storeMSAreaTags(XMPMeta meta, int item, Dimension dim) throws XMPException {
        // convert and format tag information
        DecimalFormat f = new DecimalFormat("#0.000000", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
        double xF = (double) getX();
        double yF = (double) getY();
        double wF = (double) w;
        wF = Double.parseDouble(f.format(wF));
        double hF = (double) h;
        hF = Double.parseDouble(f.format(hF));

        // corner origin (MS sets the origin to the upper left corner)
        double pixX = (dim.width * xF) - (dim.width * wF / 2);
        xF = 1.0 / dim.width * pixX;
        xF = Double.parseDouble(f.format(xF));

        double pixY = (dim.height * yF) - (dim.height * hF / 2);
        yF = 1.0 / dim.height * pixY;
        yF = Double.parseDouble(f.format(yF));

        String coordinates = xF + ", " + yF + ", " + wF + ", " + hF;
        String description = getDescription();
        String name = getName();

        // create array node
        meta.appendArrayItem(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions", new PropertyOptions().setArray(true), "", new PropertyOptions().setStruct(true));

        // store area information
        meta.setProperty(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions[" + item + "]/MPReg:Rectangle", coordinates);

        // store additional information (if there are some)
        if ((name != null && !name.isEmpty())) {
            meta.setProperty(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions[" + item + "]/MPReg:PersonDisplayName", name);
        }
        if ((description != null && !description.isEmpty())) {
            meta.setProperty(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions[" + item + "]/MPReg:PersonEmailDigest", description);
        }

        item++;
    }
}
